from tkinter.simpledialog import *
import time

__all__ = ['Vm', 'CodeError']

DIGITS = '0123456789'
OOPS = ["add", "sub", "mul", "div", "god", "jpo", "gto", "pop", "pri", "red", "hal"]

class CodeError(Exception):
    def __init__(self, i):
        self.index = i

class RunError(Exception):
    pass

def chknum(s):
    if s.startswith(('+', '-')):
        s = s[1:]
    return s and all(c in DIGITS for c in s)

class Vm:
    def __init__(self, code, que_print, que_quit):
        self.code = []
        for i, x in enumerate(code):
            x = x.strip()
            if chknum(x):
                self.code.append(int(x))
            elif x in OOPS:
                self.code.append(x)
            else:
                raise CodeError(i)
        self.stack = []
        self.pc = self.step = self.max = 0
        self.que_print = que_print
        self.que_quit = que_quit

    def run(self):
        que_print = self.que_print
        que_quit = self.que_quit
        t0 = time.time()
        que_print.append('*** START ***')
        try:
            while True:
                if que_quit:
                    return
                if not 0 <= self.pc < len(self.code):
                    raise RunError('cannot fetch next instruction')
                i = self.code[self.pc]
                if isinstance(i, int):
                    self.con(i)
                elif i == "add":
                    self.add()
                elif i == "sub":
                    self.sub()
                elif i == "mul":
                    self.mul()
                elif i == "div":
                    self.div()
                elif i == "god":
                    self.god()
                elif i == "jpo":
                    self.jpo()
                elif i == "gto":
                    self.gto()
                elif i == "pop":
                    self.pop()
                elif i == "pri":
                    self.pri()
                elif i == "red":
                    self.red()
                elif i == 'hal':
                    try:
                        self.ecode = self.stack.pop()
                        break
                    except IndexError:
                        raise RunError('hal: stack is empty')
                self.step += 1
        except RunError as e:
            que_print.append("*** ERROR [{}] *** ({x.step} steps, {x.max} stacks, {:.3f} secs)"
                                .format(e, time.time()-t0, x = self))
        else:
            if self.ecode == 0:
                que_print.append("*** HALT *** ({x.step} steps, {x.max} stacks, {:.3f} secs)"
                                    .format(time.time()-t0, x = self))
            else:
                que_print.append("*** HALT [{x.ecode}] *** ({x.step} steps, {x.max} stacks, {:.3f} secs)"
                                    .format(time.time()-t0, x = self))

    def con(self, n):
        if len(self.stack) == self.max:
            self.max += 1
        self.stack.append(n)
        self.pc += 1

    def pop(self):
        try:
            a = self.stack.pop()
        except IndexError:
            raise RunError('pop: stack is empty')
        self.pc += 1

    def add(self):
        try:
            y = self.stack.pop()
            x = self.stack.pop()
        except IndexError:
            raise RunError('add: stack is empty')
        self.stack.append(x+y)
        self.pc += 1

    def sub(self):
        try:
            y = self.stack.pop()
            x = self.stack.pop()
        except IndexError:
            raise RunError('sub: stack is empty')
        self.stack.append(x-y)
        self.pc += 1

    def mul(self):
        try:
            y = self.stack.pop()
            x = self.stack.pop()
        except IndexError:
            raise RunError('mul: stack is empty')
        self.stack.append(x*y)
        self.pc += 1

    def div(self):
        try:
            y = self.stack.pop()
            x = self.stack.pop()
        except IndexError:
            raise RunError('div: stack is empty')
        try:
            q = x//y
        except ZeroDivisionError:
            raise RunError('div: division by zero')
        if q >= 0 or x == q*y: # faster than divmod
            self.stack.append(q)
        else:
            self.stack.append(q+1)
        self.pc += 1

    def pri(self):
        try:
            a = self.stack.pop()
        except IndexError:
            raise RunError('pri: stack is empty')
        self.que_print.append(a)
        self.pc += 1

    def god(self):
        try:
            n = self.stack.pop()
        except IndexError:
            raise RunError('god: stack is empty')
        if not 0 <= n < len(self.stack):
            raise RunError('god: stack out of bounds')
        self.stack.append(self.stack[n])
        self.pc += 1

    def gto(self):
        try:
            n = self.stack.pop()
            k = self.stack.pop()
        except IndexError:
            raise RunError('gto: stack is empty')
        if not 0 <= n < len(self.stack):
            raise RunError('gto: stack out of bounds')
        self.stack[n] = k
        self.pc += 1

    def red(self):
        n = askinteger("red", "Input integer.")
        if not n:
            raise RunError('red: cannot read input')
        if len(self.stack) == self.max:
            self.max += 1
        self.stack.append(n)
        self.pc += 1

    def jpo(self):
        try:
            n = self.stack.pop()
            k = self.stack.pop()
        except IndexError:
            raise RunError('jpo: stack is empty')
        if k > 0:
            self.pc = n
        else:
            self.pc += 1
